﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Threading;
using System.Xml;
using RandomSearch.Common;
using RandomSearch.Host;
using RandomSearch.PluginsCommon;

namespace RandomSearch.VM {
    public class ViewModel : DependencyObject, IDisposable {
        private static readonly object _lockObject = new object();

        #region Properties

        public static readonly DependencyProperty StatusMessageProperty =
            DependencyProperty.Register("StatusMessage", typeof(string), typeof(ViewModel),
                                        new PropertyMetadata(""));

        public static readonly DependencyProperty ModelNotBusyProperty =
            DependencyProperty.Register("ModelIsNotBusy", typeof(bool), typeof(ViewModel),
                                        new PropertyMetadata(true));

        public static readonly DependencyProperty SelectedPluginProperty =
            DependencyProperty.Register("SelectedPlugin", typeof(string), typeof(ViewModel),
                                        new PropertyMetadata("Default"));

        public static readonly DependencyProperty FilesProperty =
            DependencyProperty.Register("Files", typeof(ObservableCollection<FileInfo>), typeof(ViewModel),
                                        new PropertyMetadata(new ObservableCollection<FileInfo>()));

        public static readonly DependencyProperty PluginsProperty =
            DependencyProperty.Register("Plugins", typeof(ObservableCollection<PluginItem>), typeof(ViewModel),
                                        new PropertyMetadata(new ObservableCollection<PluginItem>()));

        public string StatusMessage {
            get { return (string)GetValue(StatusMessageProperty); }
            set { SetValue(StatusMessageProperty, value); }
        }

        public bool ModelIsNotBusy {
            get { return (bool)GetValue(ModelNotBusyProperty); }
            set { SetValue(ModelNotBusyProperty, value); }
        }

        public string SelectedPlugin {
            get { return (string)GetValue(SelectedPluginProperty); }
            set { SetValue(SelectedPluginProperty, value); }
        }

        public ObservableCollection<PluginItem> Plugins {
            get { return (ObservableCollection<PluginItem>)GetValue(PluginsProperty); }
            set { SetValue(PluginsProperty, value); }
        }

        public ObservableCollection<FileInfo> Files {
            get { return (ObservableCollection<FileInfo>)GetValue(FilesProperty); }
            set { SetValue(FilesProperty, value); }
        }

        #endregion

        #region Commands

        private DelegateCommand<object> _cancelSearchCommand;
        private DelegateCommand<FileInfo> _openFileCommand;
        private DelegateCommand<DependencyObject> _startSearchCommand;

        public ICommand OpenFile {
            get {
                if (null == _openFileCommand)
                    _openFileCommand = new DelegateCommand<FileInfo>(CanOpenFileDelegate, OpenFileDelegate) {
                        Name = "OpenFile",
                        Text = "Open file"
                    };
                return _openFileCommand;
            }
        }

        public ICommand StartSearch {
            get {
                if (null == _startSearchCommand)
                    _startSearchCommand = new DelegateCommand<DependencyObject>(CanStartSeach, StartSearchDelegate) {
                        Name = "StartSearch",
                        Text = "Start search"
                    };
                return _startSearchCommand;
            }
        }

        public ICommand CancelSearch {
            get {
                if (null == _cancelSearchCommand)
                    _cancelSearchCommand = new DelegateCommand<object>(CanCancelSearch, StartCancelSearch) {
                        Name = "CancelSearch",
                        Text = "Cancel search"
                    };
                return _cancelSearchCommand;
            }
        }

        #endregion

        private readonly DisposableCollection _disposableEvents;
        private readonly FileSystemWatcher _pluginWatcher;
        private readonly AutoResetEvent _stopSearchEvent = new AutoResetEvent(false);
        private SearchServiceClient _client;
        private bool _disposed;
        private SearchServerDomain _domain;

        private bool _isSearchInProgress;

        public ViewModel() {
            Init(false);

            _disposableEvents = new DisposableCollection();
            string pluginsDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
            _pluginWatcher = new FileSystemWatcher(pluginsDir) { Filter = "*.dll" };

            #region Subscriptions

            //deleted
            Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(x => _pluginWatcher.Deleted += x,
                                                                                     x => _pluginWatcher.Deleted -= x)
                .Subscribe(x => PluginDeleted(x.EventArgs.FullPath))
                .AddTo(_disposableEvents);

            Observable.FromEventPattern<RenamedEventHandler, RenamedEventArgs>(x => _pluginWatcher.Renamed += x,
                                                                               x => _pluginWatcher.Renamed -= x)
                .Subscribe(x => PluginDeleted(x.EventArgs.FullPath))
                .AddTo(_disposableEvents);

            //changed
            Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(x => _pluginWatcher.Created += x,
                                                                                     x => _pluginWatcher.Created -= x)
                .Subscribe(x => PluginAdded(x.EventArgs.FullPath))
                .AddTo(_disposableEvents);

            #endregion

            _pluginWatcher.EnableRaisingEvents = true;
        }

        #region IDisposable Members

        public void Dispose() {
            if (_disposed) return;
            if (null != _client) _client.Dispose();
            if (null != _domain) _domain.Dispose();
            if (null != _disposableEvents) _disposableEvents.Dispose();
            if (null != _pluginWatcher) _pluginWatcher.EnableRaisingEvents = false;
            _disposed = true;
        }

        #endregion

        private void SetStatus(string message) {
            DoOnUi(() => { StatusMessage = message; });
        }

        private T GetFromUi<T>(Func<T> foo) {
            T res = default(T);
            Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { res = foo(); }));
            return res;
        }

        private void DoOnUi(Action foo) {
            Dispatcher.Invoke(DispatcherPriority.Normal, foo);
        }

        private void PluginDeleted(string file) {
            if (GetFromUi(() => ModelIsNotBusy)) {
                if (_isSearchInProgress) StartCancelSearch(null);
                SetStatus("Plugin deleted. Reloading...");
                DoOnUi(() => { ModelIsNotBusy = false; });
                Init();
            }
        }

        private void PluginAdded(string file) {
            if (GetFromUi(() => ModelIsNotBusy)) {
                if (_isSearchInProgress) StartCancelSearch(null);
                SetStatus("Plugin added. Reloading...");
                DoOnUi(() => {
                    ModelIsNotBusy = false;
                    ReloadPluginList();
                    ModelIsNotBusy = true;
                });
            }
        }

        private void Init(bool async = true) {
            var foo = new Action(() => {
                ReloadProxies();
                DoOnUi(ReloadPluginList);
            });

            if (async) {
                var t = new Task(foo);
                SetStatus("Loading server...");
                t.Start();
            }
            else {
                foo();
            }
        }

        private void ReloadPluginList() {
            SetStatus("Loading plugins...");

            Plugins.Clear();
            IEnumerable<SearchPluginData> plugins = _client.GetPlugins();
            foreach (SearchPluginData plugin in plugins) {
                UIElement settings = null;
                if (!string.IsNullOrWhiteSpace(plugin.SettingsXAML)) {
                    var stringReader = new StringReader(plugin.SettingsXAML);
                    XmlReader xmlReader = XmlReader.Create(stringReader);
                    settings = (UIElement)XamlReader.Load(xmlReader);
                }
                Plugins.Add(new PluginItem {
                    Id = plugin.PluginId,
                    Name = plugin.DisplayName,
                    Settings = settings
                });
            }
            SelectedPlugin = "Default";
            SetStatus("Ready");
            ModelIsNotBusy = true;
        }

        private void ReloadProxies() {
            if (null != _client) _client.Dispose();
            if (null != _domain) _domain.Dispose();

            var uri = new Uri("net.pipe://localhost/PipeRandomSearch/{0}".ƒ(Guid.NewGuid()));
            _domain = new SearchServerDomain(uri);
            _client = new SearchServiceClient(uri);
            _client.Init();
        }

        private bool CanOpenFileDelegate(FileInfo obj) {
            return File.Exists(obj.FullName);
        }

        private void OpenFileDelegate(FileInfo obj) {

            //MessageBox.Show(obj.FullName);
            Process.Start(new ProcessStartInfo {
                FileName = "explorer.exe",
                Arguments = "/select,\"{0}\"".ƒ(obj.FullName)

            });

        }

        private void StartSearchDelegate(DependencyObject ctrl) {
            Files.Clear();

            #region Settings

            DependencyObject basicOptions = LogicalTreeHelper.FindLogicalNode(ctrl, "Options");

            Dictionary<string, string> options = OptionExtractor.GetOptions(basicOptions);
            var o = new SearchOptions();
            o.PluginId = SelectedPlugin;

            o.PathToRootFolder = options["SettingsPath"];
            o.IncludeSubfolders = bool.Parse(options["SettingsIncludeSubfolder"]);

            if (options.ContainsKey("SettingsMinSize"))
                o.MinSize = long.Parse(options["SettingsMinSize"]);

            if (options.ContainsKey("SettingsMaxSize"))
                o.MaxSize = long.Parse(options["SettingsMaxSize"]);

            if (options.ContainsKey("SettingsMinDate"))
                o.DateFrom = DateTime.Parse(options["SettingsMinDate"]);

            if (options.ContainsKey("SettingsMaxDate"))
                o.DateTo = DateTime.Parse(options["SettingsMaxDate"]);

            bool ro = bool.Parse(options["SettingsReadOnly"]);
            bool ar = bool.Parse(options["SettingsArchive"]);
            bool co = bool.Parse(options["SettingsCompressed"]);

            if (ro || ar || co) {
                o.RequiredAttributes = 0;
                if (ro) o.RequiredAttributes |= FileAttributes.ReadOnly;
                if (ar) o.RequiredAttributes |= FileAttributes.Archive;
                if (co) o.RequiredAttributes |= FileAttributes.Compressed;
            }

            #endregion

            o.Settings = options;

            _client.StartSearch(o);
            var t = new Task(ReadSearchResults);
            t.Start();
            SetStatus("Search in progress...");
            _isSearchInProgress = true;
        }

        private void ReadSearchResults() {
            bool completed = false;
            while (!_stopSearchEvent.WaitOne(TimeSpan.FromSeconds(0.5d)) && !completed) {
                IEnumerable<FileInfo> files = _client.GetReadyResults(ref completed);
                if (null != files) {
                    var foo = new Action(() => {
                        foreach (FileInfo f in files)
                            Files.Add(f);
                    });
                    DoOnUi(foo);
                }
            }
            SetStatus("Search completed");
            _isSearchInProgress = false;
        }

        private bool CanStartSeach(DependencyObject _) {
            return !_isSearchInProgress && ModelIsNotBusy;
        }

        private bool CanCancelSearch(object p) {
            return _isSearchInProgress && ModelIsNotBusy;
        }

        private void StartCancelSearch(object p) {
            _client.CancelSearch();
            _stopSearchEvent.Set();
            SetStatus("Search canceled");
            _isSearchInProgress = false;

        }

        ~ViewModel() {
            Dispose();
        }
    }
}